package modelarts

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
)

const (
	DEFAULT_CODEDIR  = "train/trainDir/"
	DEFAULT_BOOTFILE = "train.py"
	DEFAULT_LOG_URL  = "train/log/"
	DEFAULT_CONFIG   = "train/config.json"
)

var (
	ZhTime, _         = time.LoadLocation("Asia/Shanghai")
	VersionAscendList = []AscendVersion{{ID: "modelarts.kat1.xlarge.public", Name: "Ascend: 1*Ascend 910(32GB)|ARM: 24核 96GB 3200GB"}, {ID: "modelarts.kat1.8xlarge.public", Name: "Ascend: 8*Ascend 910(32GB)|ARM: 192核 768GB 3200GB"}}
	VersionNVIDIAList = []AscendVersion{{ID: "modelarts.p3.8xlarge.public", Name: "GPU:8*NVIDIA-V100(32GB)|CPU:72核 512GB 3200 GB"}, {ID: "modelarts.p3.large.public", Name: "GPU:1*NVIDIA-V100(32GB)|CPU:8核 64GB 3200 GB"}}
)

type AscendVersion struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// 查询得到的训练任务列表
type JobList struct {
	// 查询到当前用户名下的所有作业总数。
	Total int `json:"total"`
	// 查询到当前用户名下的所有符合查询条件的作业总数。
	Count int `json:"count"`
	// 	查询作业的限制量。最小为1，最大为50。
	Limit int `json:"limit"`
	// 查询作业的偏移量，最小为0。例如设置为1，则表示从第二条开始查。
	Offset int `json:"offset"`
	// 	查询作业排列顺序的指标。默认使用create_time排序。
	SortBY string `json:"sort_by"`
	// 查询作业排列顺序，默认为“desc”，降序排序。也可以选择对应的“asc”,升序排序。
	Order string `json:"order"`
	// 查询作业要搜索的分组条件。
	GroupBy string `json:"group_by"`
	// 指定作业所处的工作空间，默认值为“0”。
	WorkspaceID string `json:"workspace_id"`
	// 作业所属的ai项目，默认值为"default-ai-project"。
	AiProject string `json:"ai_project"`
	// 查询到当前用户名下的所有符合查询条件的作业详情。
	Items []JobDetail `json:"items"`
}

type Job struct {
	// 训练作业类型。
	Kind string `json:"kind"`
	// 	训练作业元信息。
	Metadata JobMetadata `json:"metadata"`
	//训练作业算法。目前支持三种形式：
	//id 只取算法的id；
	//subscription_id+item_version_id 取算法的订阅id和版本id；
	//code_dir+boot_file 取训练作业的代码目录和启动文件。
	Algorithm JobAlgorithmResponse `json:"algorithm"`
	// 训练作业规格参数。有此字段时，无需填写tasks字段。
	Spec JobSpec `json:"spec"`
	//异构训练作业的任务列表。有此字段时，无需填写spec字段。
	Tasks []TaskResponse `json:"tasks,omitempty"`
}

// 查询job的返回信息
type JobDetail struct {
	Job
	Status *JobStatus `json:"status,omitempty"`
}

// 查询job的返回信息
type JobLogsPreview struct {
	Content     string `json:"content"`
	CurrentSize int    `json:"current_size"`
	FullSize    int    `json:"full_size"`
}

// 	训练作业元信息。
type JobMetadata struct {
	Id string `json:"id"`
	// 训练作业名称。限制为1-64位只含数字、字母、下划线和中划线的名称。
	Name string `json:"name"`
	// 对训练作业的描述，默认为“NULL”，字符串的长度限制为[0, 256]。
	Description string `json:"description"`
	// 训练作业创建时间戳，单位为毫秒，创建成功后由modelarts生成返回，无需填写。
	CreateTime int `json:"create_time"`
	// 	指定作业所处的工作空间，默认值为“0”。
	WorkspaceID                 string          `json:"workspace_id"`
	AiProject                   string          `json:"ai_project"`
	UserName                    string          `json:"user_name"`
	Annotations                 *JobAnnotations `json:"annotations,omitempty"`
	TrainingExperimentReference interface{}     `json:"training_experiment_reference"`
}
type JobStatus struct {
	// 训练作业一级状态，状态值稳定不变，可选值如下： “Creating”， “Pending”， “Running”， “Failed”， “Completed”， “Terminating”， “Terminated”， “Abnormal”。
	Phase string `json:"phase"`
	// 训练作业二级状态，状态值不稳定，可选值如下： “Creating”， “Queuing”， “Running”， “Failed”， “Completed”， “Terminating”， “Terminated”， “CreateFailed”， “TerminatedFailed”， “Unknown”， “Lost”。
	SecondaryPhase string `json:"secondary_phase"`
	// 训练作业运行时长，单位为毫秒。
	Duration int  `json:"duration"`
	IsHanged bool `json:"is_hanged"`
	// 训练作业开始时间，格式为时间戳。
	StartTime int `json:"start_time"`
	// 训练作业子任务名称。
	Tasks []string `json:"tasks,omitempty"`
	// 训练作业子任务名称。
	TaskStatuses []JobTaskStatuses `json:"task_statuses,omitempty"`
}

type JobTaskStatuses struct {
	// 训练作业子任务名称。
	Task string `json:"task"`
	// 训练作业二级状态，状态值不稳定，可选值如下： “Creating”， “Queuing”， “Running”， “Failed”， “Completed”， “Terminating”， “Terminated”， “CreateFailed”， “TerminatedFailed”， “Unknown”， “Lost”。
	Message string `json:"message"`
	// 训练作业子任务退出码。
	ExitCode int `json:"exit_code"`
}

type JobAnnotations struct {
	JobTemplate string `json:"job_template"`
	KeyTask     string `json:"key_task"`
}

// 训练作业算法
type JobAlgorithmResponse struct {
	// 训练作业的代码目录。如：“/usr/app/”。应与boot_file一同出现，若填入id或subscription_id+item_version_id则无需填写。
	CodeDir string `json:"code_dir"`
	// 训练作业的代码启动文件，需要在代码目录下。如：“/usr/app/boot.py”。应与code_dir一同出现，若填入入id或subscription_id+item_version_id则无需填写。
	BootFile string `json:"boot_file"`
	//训练作业的数据输入。
	Inputs []JobAlgorithmInput `json:"inputs"`
	// 训练作业的结果输出。
	Outputs []JobAlgorithmInput `json:"outputs"`
	// 训练作业的引擎。使用算法管理的算法id或订阅算法subscription_id+item_version_id创建作业时，无需填写。
	Engine JobAlgorithmEngine `json:"engine"`
	// 自动化搜索作业的yaml配置路径，需要提供一个OBS路径。
	Autosearch_config_path string `json:"autosearch_config_path,omitempty"`

	// 自动化搜索作业的框架代码目录，需要提供一个OBS路径。
	Autosearch_framework_path string `json:"autosearch_framework_path,omitempty"`
	// 自定义镜像训练作业的自定义镜像的容器的启动命令。可填code_dir
	Command string `json:"command"`
	// 训练作业的运行参数。
	Parameters   []Parameter `json:"parameters,omitempty"`
	Policies     interface{} `json:"policies,omitempty"`
	Environments interface{} `json:"environments,omitempty"`
}
type Parameter struct {
	// 参数名称。
	Name string `json:"name"`
	// 参数值。
	Value string `json:"value"`
	// 参数描述信息。
	Description string `json:"description"`
	// 参数属性。
	Constraint Constraint `json:"constraint"`
}
type Constraint struct {
	// 参数种类。
	Type string `json:"type"`
	// 是否可编辑。
	Editable bool `json:"editable"`
	// 是否必须。
	Required bool `json:"required"`
	// 是否敏感。
	Sensitive bool `json:"sensitive"`
	// 有效种类。
	ValidType string `json:"valid_type"`
	// 有效范围。
	ValidRange []string `json:"valid_range"`
}

type JobAlgorithmInput struct {
	// 	数据输入通道名称。
	Name string `json:"name"`
	// 数据输入通道映射的容器本地路径。
	LocalDir *string `json:"local_dir,omitempty"`
	// 数据实际输入信息。枚举值：
	// dataset 指定输入为数据集；
	// obs 指定输入为OBS路径。
	Remote JobAlgorithmInputRemote `json:"remote"`
}
type JobAlgorithmEngine struct {
	// 训练作业选择的引擎规格ID。engine_id，engine_name + engine_version和image_url方式三选一。
	EngineId string `json:"engine_id"`
	// 	训练作业选择的引擎名称。若填入engine_id则无需填写。
	EngineName string `json:"engine_name"`
	// 训练作业选择的引擎版本名称。若填入engine_id则无需填写。
	EngineVersion string `json:"engine_version"`
	V1Compatible  bool   `json:"v1_compatible"`
	RunUser       string `json:"run_user"`
}
type AlgorithmEngineList struct {
	Total int                   `json:"total"`
	Items []*JobAlgorithmEngine `json:"items"`
}

type JobAlgorithmInputRemote struct {
	Obs JobAlgorithmInputRemoteObs `json:"obs"`
}
type JobAlgorithmInputRemoteObs struct {
	ObsUrl string      `json:"obs_url"`
	Config interface{} `json:"config,omitempty"`
}
type JobSpec struct {
	Resource      JobSpecResource      `json:"resource"`
	LogExportPath JobSpecLogExportPath `json:"log_export_path"`
	IsHostedLog   bool                 `json:"is_hosted_log"`
}
type JobFlavors struct {
	Flavors    []*JobSpecFlavor `json:"flavors"`
	TotalCount int              `json:"total_count"`
}
type JobSpecResource struct {
	Policy       string                       `json:"policy"`
	FlavorId     string                       `json:"flavor_id"`
	FlavorName   string                       `json:"flavor_name"`
	NodeCount    int                          `json:"node_count"`
	FlavorDetail *JobSpecResourceFlavorDetail `json:"flavor_detail,omitempty"`
}

type JobSpecFlavor struct {
	JobSpecResourceFlavorDetail
	FlavorId   string `json:"flavor_id"`
	FlavorName string `json:"flavor_name"`
	NodeCount  int    `json:"node_count"`
}
type FlavorResponse struct {
	JobSpecFlavor
	Attributes map[string]interface{} `json:"attributes"`
	MaxNum     int                    `json:"max_num"`
}

type JobSpecLogExportPath struct {
	ObsUrl string `json:"obs_url"`
}
type JobSpecResourceFlavorDetail struct {
	FlavorType string                                `json:"flavor_type"`
	Billing    JobSpecResourceFlavorDetailBilling    `json:"billing"`
	FlavorInfo JobSpecResourceFlavorDetailFlavorInfo `json:"flavor_info"`
}

type JobSpecResourceFlavorDetailBilling struct {
	Code    string `json:"code"`
	UnitNum int    `json:"unit_num"`
}

type JobSpecResourceFlavorDetailFlavorInfo struct {
	MaxNum int                                         `json:"max_num"`
	Cpu    JobSpecResourceFlavorDetailFlavorInfoCpu    `json:"cpu"`
	Gpu    JobSpecResourceFlavorDetailFlavorInfoGpu    `json:"gpu"`
	Npu    JobSpecResourceFlavorDetailFlavorInfoGpu    `json:"npu"`
	Memory JobSpecResourceFlavorDetailFlavorInfoMemory `json:"memory"`
	Disk   JobSpecResourceFlavorDetailFlavorInfoMemory `json:"disk"`
}

type JobSpecResourceFlavorDetailFlavorInfoCpu struct {
	Arch    string `json:"arch"`
	CoreNum int    `json:"core_num"`
}
type JobSpecResourceFlavorDetailFlavorInfoGpu struct {
	UnitNum     int    `json:"unit_num"`
	ProductName string `json:"product_name"`
	Memory      string `json:"memory"`
}
type JobSpecResourceFlavorDetailFlavorInfoMemory struct {
	Size int    `json:"size"`
	Unit string `json:"unit"`
}
type JobInputParam struct {
	Name     string `json:"name"`
	InputUrl string `json:"input_url"`
}
type JobOutputParam struct {
	Name      string `json:"name"`
	OutputDir string `json:"output_dir"`
}
type JobHyperParam struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}
type TaskResponse struct {
	Role         string               `json:"role"`
	Algorithm    JobAlgorithmResponse `json:"algorithm"`
	TaskResource FlavorResponse       `json:"task_resource"`
}

type JobErrorMsg struct {
	ErrorCode     string `json:"error_code"`
	ErrorMsg      string `json:"error_msg"`
	ErrorSolution string `json:"error_solution"`
}

// modelArts的项目
type ModelArtsProject struct {
	ProjectID      string
	Region         string
	EndPointUrl    string
	Manager        *ProjectManager
	TokenValue     string
	Token          *HWcloudToken
	obsClient      *obs.ObsClient
	obsEndPointUrl string
}
type XiHeProject struct {
	ModelArtsProject
	OwnerName   string
	ProjectName string
}

type JobConfig struct {
	SDK string `json:"SDK"`
	// 	训练作业的代码目录。如：“/usr/app/”。应与boot_file一同出现，若填入id或subscription_id+item_version_id则无需填写。
	CodeDir string `json:"code_dir"`
	// 	训练作业的代码启动文件，需要在代码目录下。如：“/usr/app/boot.py”。应与code_dir一同出现，若填入入id或subscription_id+item_version_id则无需填写。
	BootFile string `json:"boot_file"`
	// 训练作业的结果输出。
	OutputDir   string `json:"output_dir"`
	InputParam  string `json:"-"`
	OutputParam string `json:"-"`
	// 日志路径
	LogUrl string `json:"log_url"`
	// 训练的名字
	JobName string `json:"job_name"`
	// 描述
	JobDescription string     `json:"job_description"`
	Frameworks     Frameworks `json:"frameworks"`

	// 超参在您的算法代码中除了输入、输出和日志参数，其它需传入的参数，比如学习率、迭代次数等，此参数将会用于自动评估中上下文信息的显示。
	// 格式为：
	// [{
	// "name": <解析参数对应的值1>,
	//     "value":<解析参数名称1>
	//   },{
	//     "name": <解析参数对应的值2>,
	//     "value": <解析参数名称2>
	// }, ... ]
	// 注意{}末尾不能有逗号
	Hypeparameters    []JobHyperParam        `json:"hypeparameters"`
	HypeParam         string                 `json:"-"`
	EnvVariables      map[string]interface{} `json:"env_variables"`
	EnvParam          string                 `json:"-"`
	TrainInstanceType string                 `json:"train_instance_type"`
	Inputs            []JobInputParam        `json:"inputs"`
	Outputs           []JobOutputParam       `json:"outputs"`
}
type Frameworks struct {
	FrameworkType    string `json:"framework_type"`
	FrameworkVersion string `json:"framework_version"`
}

// 输出给羲和前端的显示数据
type DisplayJob struct {
	TrainID     string `json:"train_id"`
	InsanceName string `json:"insance_name"`
	CreateTime  string `json:"create_time"`
	Status      string `json:"status"`
	Runtime     string `json:"runtime"`
	EngineName  string `json:"engine_name"`
	Description string `json:"description"`
	NodeCount   string `json:"node_count"`
	Flavor      string `json:"flavor"`
	// Configfile  string `json:"configfile"`
	LogFile string `json:"log_file"`
}
type TrainJobParam struct {
	// 华为云的账户名
	HwAccountName string
	// 华为云的账户id
	HwAccountID string
	// 项目id
	ProjectID string
	// 区域
	Region string
	// Iam 账户名字
	IamName string
	// Iam 账户的密码
	IamPswd string
	// Iam 账户的access key
	IamAK string
	// Iam 账户的secret key
	IamSK string
	// obs 桶的根目录
	ObsBucketPath string
}

func (trainConfig *JobConfig) ParseParam(project *ModelArtsProject, xiheUserName, xiheProjectName string) (inputParamStr, outputParamStr string, err error) {
	//---------------JobInputParam
	var inputParamList []JobInputParam
	err = json.Unmarshal([]byte(trainConfig.InputParam), &inputParamList)
	if err != nil {
		err = fmt.Errorf("输入配置 格式不对:%s ", err.Error())
		return
	}
	for _, inputParam := range inputParamList {
		inputParamStr = inputParamStr + `{
				"name": "` + inputParam.Name + `",  
				"remote": {
					"obs": {
						"obs_url": "` + project.Manager.ObsBucketName + `/` + inputParam.InputUrl + `"
					}
				}
			},`
	}
	inputParamStr = `"inputs": [` + inputParamStr[0:strings.LastIndex(inputParamStr, ",")] + `]`
	//-----------JobOutputParam
	var outputParamList []JobOutputParam
	err = json.Unmarshal([]byte(trainConfig.OutputParam), &outputParamList)
	if err != nil {
		err = fmt.Errorf("输出配置 格式不对:%s ", err.Error())
		return
	}
	for _, outputParam := range outputParamList {
		outputParamStr = outputParamStr + `{
				"name": "` + outputParam.Name + `",  
				"remote": {
					"obs": {
						"obs_url": "` + project.Manager.ObsBucketName + `/projects/` + xiheUserName + `/` + xiheProjectName + `/` + outputParam.OutputDir + `"
					}
				}
			},`
	}
	outputParamStr = `"outputs": [` + outputParamStr[0:strings.LastIndex(outputParamStr, ",")] + `]`
	//-----------EnvVariables
	envMap := make(map[string]interface{}, 0)
	err = json.Unmarshal([]byte(trainConfig.EnvParam), &envMap)
	if err != nil {
		err = fmt.Errorf("环境变量environments 格式不对:%s ", err.Error())
		return
	}
	trainConfig.EnvParam = `,"environments":` + trainConfig.EnvParam

	return
}

// 根据前端发过来的json   创建新的job
func (project *ModelArtsProject) CreateTrainingJobByJson(xiheUserName, xiheProjectName, paramJson string, maxJob int) (jobDetail *JobDetail, err error) {
	var (
		jobConfig  JobConfig
		engine     JobAlgorithmEngine
		bestFlavor JobSpecFlavor
	)
	err = json.Unmarshal([]byte(paramJson), &jobConfig)
	if err != nil {
		err = fmt.Errorf(" json.Unmarshal error:%v", err)
		return
	}
	if jobConfig.Frameworks.FrameworkType != "MPI" && jobConfig.Frameworks.FrameworkType != "Ascend-Powered-Engine" {
		err = fmt.Errorf(" 目前只支持MPI 和Ascend-Powered-Engine 的frameworks,请核对 ")
		return
	}

	engine.EngineName = jobConfig.Frameworks.FrameworkType
	engine.EngineId = jobConfig.Frameworks.FrameworkVersion
	engine.EngineVersion = jobConfig.Frameworks.FrameworkVersion
	trainingJobFlavors, err := project.ListTrainingJobFlavors("")
	if err != nil {
		err = fmt.Errorf(" ListTrainingJobFlavors error:%v", err)
		return
	}
	for _, flavor := range trainingJobFlavors.Flavors {
		// 改成 参数判断。
		if flavor.FlavorId == jobConfig.TrainInstanceType {
			bestFlavor = *flavor
			break
		}
	}
	inputParamBytes, err := json.Marshal(jobConfig.Inputs)
	if err != nil {
		err = fmt.Errorf(" Inputs Marshal error:%v", err)
		return
	}
	jobConfig.InputParam = string(inputParamBytes)

	outputParamBytes, err := json.Marshal(jobConfig.Outputs)
	if err != nil {
		err = fmt.Errorf(" Outputs Marshal error:%v", err)
		return
	}
	jobConfig.OutputParam = string(outputParamBytes)
	envParamBytes, err := json.Marshal(jobConfig.EnvVariables)
	if err != nil {
		err = fmt.Errorf(" EnvVariables vMarshal error:%v", err)
		return
	}
	jobConfig.EnvParam = string(envParamBytes)
	for _, hypeparameters := range jobConfig.Hypeparameters {
		if hypeparameters.Name == "" || hypeparameters.Value == "" {
			err = fmt.Errorf(" hypeparameters format error")
			return
		}
	}
	hypeParamBytes, err := json.Marshal(jobConfig.Hypeparameters)
	if err != nil {
		err = fmt.Errorf(" Hypeparameters Marshal error: %s", err.Error())
		return
	}
	jobConfig.HypeParam = string(hypeParamBytes)
	jobDetail, err = project.CreateTrainingJob(&jobConfig, &engine, &bestFlavor, xiheUserName, xiheProjectName, maxJob)
	return
}

// 创建新的job
func (project *ModelArtsProject) CreateTrainingJob(jobConfig *JobConfig, engine *JobAlgorithmEngine, flavor *JobSpecFlavor, xiheUserName, projectname string, maxJob int) (jobDetail *JobDetail, err error) {
	//在创建之间 先去查一下当前的羲和用户下面有多少个已经训练的任务。
	maxAllowedList, err := project.ListTrainingJobs(xiheUserName, projectname, 0, maxJob)
	if err != nil {
		return
	}
	if len(maxAllowedList.Items) >= maxJob {
		err = fmt.Errorf("创建失败\n失败信息：一个仓库最多只有%d个训练实例，请删除不必要的训练实例后，再创建", maxJob)
		return
	}
	for _, item := range maxAllowedList.Items {
		jobItem := item.Format()
		if jobItem.Status == "Running" {
			err = fmt.Errorf("创建失败\n失败信息：一个仓库最多只有1个运行中训练实例，请运行完成后再创建训练实例")
			return
		}
	}

	inputParamStr, outputParamStr, err := jobConfig.ParseParam(project, xiheUserName, projectname)
	if err != nil {
		return
	}

	// 训练作业选择的资源副本数。最小值：1
	if flavor.NodeCount <= 0 {
		flavor.NodeCount = 1
	}
	if len(jobConfig.Hypeparameters) > 0 {
		jobConfig.HypeParam = `,"hypeparameters":` + jobConfig.HypeParam
	} else {
		jobConfig.HypeParam = ``
	}
	if len(jobConfig.EnvVariables) <= 0 {
		jobConfig.EnvParam = `,"environments":{}`
	}

	// 构造输入参数
	data := `{
		"kind": "job",
		"metadata": { 
			"name": "` + xiheUserName + `---` + projectname + `---` + jobConfig.JobName + `",
			"description":  "` + jobConfig.JobDescription + `" 
		},
		"algorithm": {
			"code_dir": "` + project.Manager.ObsBucketName + `/projects/` + xiheUserName + `/` + projectname + `/` + jobConfig.CodeDir + `",
			"boot_file": "` + project.Manager.ObsBucketName + `/projects/` + xiheUserName + `/` + projectname + `/` + jobConfig.CodeDir + jobConfig.BootFile + `",
			` + inputParamStr + `,
			` + outputParamStr + `,
			"engine": {
				"engine_id": "` + engine.EngineId + `",
				"engine_name": "` + engine.EngineName + `",
				"engine_version":"` + engine.EngineVersion + `"
			}` + jobConfig.HypeParam + jobConfig.EnvParam + ` 
		},
		"tasks": [
			{
			  "task_resource": {
				"node_count": ` + strconv.Itoa(flavor.NodeCount) + `
			  }
			}
		],
		"spec": {
			"resource": { 
				"flavor_id":"` + flavor.FlavorId + `",
				"node_count": ` + strconv.Itoa(flavor.NodeCount) + `
			},
			"log_export_path": {
				"obs_url": "` + project.Manager.ObsBucketName + `/projects/` + xiheUserName + `/` + projectname + `/` + DEFAULT_LOG_URL + `"
			}
		}
	}`

	jobDetail, err = project.buildTrainJob(data)
	if err != nil {
		return nil, err
	}
	//todo: 创建成功后 需要把 config.json 需要上传到obs 桶里
	jobConfigBytes, _ := json.Marshal(jobConfig)
	// 地址  xihe-obj/projects/username/project/train/config.json
	configJson := &obs.PutObjectInput{}
	obspathArray := strings.Split(project.Manager.ObsBucketName, "/")
	if len(obspathArray) < 2 {
		err = fmt.Errorf(" obs桶名异常，请检查 :%v", project.Manager.ObsBucketName)
		return
	}
	obspath := obspathArray[1]
	configJson.Bucket = obspath
	configJson.Key = fmt.Sprintf("%s/projects/%s/%s/%s", obspathArray[2], xiheUserName, projectname, DEFAULT_CONFIG)
	configJson.Body = strings.NewReader(string(jobConfigBytes))
	_, err = project.obsClient.PutObject(configJson)
	defer project.obsClient.Close()
	if err != nil {
		err = fmt.Errorf(" 创建任务成功，但 config.json 上传失败:%v", err)
		return
	}

	return
}

// 调用modelArts接口执行构建任务
func (project *ModelArtsProject) buildTrainJob(param string) (jobdetail *JobDetail, err error) {
	body := bytes.NewBufferString(param)
	url := fmt.Sprintf(project.EndPointUrl + "/training-jobs")
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusCreated {
		err = fmt.Errorf("创建失败:%s", string(respBodyBytes))
		return nil, err
	}
	jobdetail = new(JobDetail)
	err = json.Unmarshal(respBodyBytes, jobdetail)
	if err != nil {
		fmt.Println(string(respBodyBytes))
		return nil, err
	}
	return
}

//  重构一个训练实例
//	使用的配置参数完全等同被重构的job，除了名字追加一个_copy
func (project *ModelArtsProject) RebuildTrainingJobs(jobid string) (jobDetail *JobDetail, err error) {
	jobDetail, err = project.ShowTrainingJobDetails(jobid)
	if err != nil {
		err = fmt.Errorf("获取原来的job异常:%s", err.Error())
		return
	}
	now := time.Now()
	jobDetail.Spec.Resource.FlavorDetail = nil
	jobDetail.Tasks = nil
	jobDetail.Metadata.Annotations = nil
	for _, output := range jobDetail.Algorithm.Outputs {
		output.LocalDir = nil
	}
	for _, input := range jobDetail.Algorithm.Inputs {
		input.LocalDir = nil
	}
	jobDetail.Metadata.CreateTime = 0
	jobDetail.Status = nil
	jobDetail.Metadata.Name = jobDetail.Metadata.Name + "_copy_" + now.Format("0102150405")
	jobDetailBytes, _ := json.Marshal(jobDetail)
	jobDetail, err = project.buildTrainJob(string(jobDetailBytes))
	if err != nil {
		err = fmt.Errorf("重构job的时候异常:%s", err.Error())
		return
	}
	return
}

//  获取羲和项目的 job 列表,
// xiheUserName 羲和的用户名
// xiheProjectName 羲和的项目名
//	offset 列表的起始页
//	limit 每页显示的条数
func (project *ModelArtsProject) ListTrainingJobs(xiheUserName, xiheProjectName string, offset, limit int) (jobList *JobList, err error) {
	data := fmt.Sprintf(`{
		"offset": %d,
		"limit": %d,
		"sort_by": "create_time",
		"order": "desc",
		"filters": [
			{
			"key": "name",
			"operator": "like",
				"value": [
					"%s---%s---"
				]
			}
		]
	   }`, offset, limit, xiheUserName, xiheProjectName)
	body := bytes.NewBufferString(data)
	url := fmt.Sprintf(project.EndPointUrl + "/training-job-searches")
	if limit <= 0 { //123
		//如果不设置条数，则不传参。可以全部获取  like '%s'    123efg.  abc123
		body = bytes.NewBufferString("")
	}
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	jobList = new(JobList)
	err = json.Unmarshal(respBodyBytes, jobList)
	if err != nil {
		return nil, err
	}
	return jobList, nil
}

//  获取job详情
func (project *ModelArtsProject) ShowTrainingJobDetails(job_id string) (jobDetail *JobDetail, err error) {
	url := fmt.Sprintf(project.EndPointUrl + "/training-jobs/" + job_id)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	jobDetail = new(JobDetail)
	err = json.Unmarshal(respBodyBytes, jobDetail)
	if err != nil {
		return nil, err
	}

	return
}

//  查询训练作业指定任务的日志(预览)
func (project *ModelArtsProject) ShowTrainingJobLogsPreview(job_id string, taskid_list ...string) (jobLogsPreview *JobLogsPreview, err error) {
	taskidStr := "worker-0"
	if len(taskid_list) > 0 {
		taskidStr = taskid_list[0]
	}
	url := fmt.Sprintf(project.EndPointUrl + "/training-jobs/" + job_id + "/tasks/" + taskidStr + "/logs/preview")
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf(string(respBodyBytes))
	}
	jobLogsPreview = new(JobLogsPreview)
	err = json.Unmarshal(respBodyBytes, jobLogsPreview)
	if err != nil {
		return nil, err
	}
	return
}

//  获取job 的logs链接
func (project *ModelArtsProject) ShowObsUrlOfTrainingJobLogs(job_id string, taskid_list ...string) (logurl string, err error) {
	taskidStr := "worker-0"
	if len(taskid_list) > 0 {
		taskidStr = taskid_list[0]
	}
	url := project.EndPointUrl + "/training-jobs/" + job_id + "/tasks/" + taskidStr + "/logs/url"
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	client := new(http.Client)
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf(http.StatusText(http.StatusOK))
	}
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	result := make(map[string]string)
	err = json.Unmarshal(respBodyBytes, &result)
	if err != nil {
		return "", err
	}
	return result["obs_url"], nil
}

// 只更新job 的描述信息。
func (project *ModelArtsProject) ChangeTrainingJobDescription(job_id, newDesc string) (err error) {
	data := fmt.Sprintf(`{
		"description": "%s"
	   }`, newDesc)
	body := bytes.NewBufferString(data)
	url := project.EndPointUrl + "/training-jobs/" + job_id
	req, err := http.NewRequest("PUT", url, body)
	if err != nil {
		return err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	client := new(http.Client)
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf(string(respBodyBytes))
	}
	return nil
}

//	获取训练任务的资源规格列表
func (project *ModelArtsProject) ListTrainingJobFlavors(flavor_type string) (result *JobFlavors, err error) {
	if len(flavor_type) > 0 {
		switch flavor_type {
		case "CPU":
			fallthrough
		case "GPU":
			fallthrough
		case "Ascend":
		default:
			return nil, fmt.Errorf("flavor_type 只能是CPU,GPU 或者Ascend 之一")
		}
		flavor_type = "?flavor_type=" + flavor_type
	}

	url := project.EndPointUrl + "/training-job-flavors" + flavor_type
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	client := new(http.Client)
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf(string(respBodyBytes))
	}
	result = new(JobFlavors)
	err = json.Unmarshal(respBodyBytes, &result)
	if err != nil {
		return nil, err
	}
	return result, nil

}

//	获取训练任务的引擎列表
func (project *ModelArtsProject) ListTrainingJobEngines() (result *AlgorithmEngineList, err error) {
	url := project.EndPointUrl + "/training-job-engines"
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")

	client := new(http.Client)
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf(string(respBodyBytes))
	}
	result = new(AlgorithmEngineList)
	err = json.Unmarshal(respBodyBytes, &result)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// 终止job
func (project *ModelArtsProject) StopTrainingJob(job_id string) error {
	body := bytes.NewBufferString(`{
		"action_type":"terminate"
	   }`)
	url := project.EndPointUrl + "/training-jobs/" + job_id + "/actions"
	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		return err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("StopTrainingJob  [%s] failed :%s ", job_id, string(respBodyBytes))
	}
	return nil
}

// 删除job
func (project *ModelArtsProject) DeleteTrainingJob(job_id string) error {
	jobDetail, err := project.ShowTrainingJobDetails(job_id)
	if err != nil {
		return err
	}
	url := project.EndPointUrl + "/training-jobs/" + job_id
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	req.Header.Add("X-Auth-Token", project.TokenValue)
	req.Header.Add("Content-Type", "application/json;charset=UTF-8")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	respBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("DeleteTrainingJob  [%s] failed :%s ", job_id, string(respBodyBytes))
	}

	//------还要删除所占用的日志
	// 先获取 桶下面的所有日志。
	nameList := strings.Split(jobDetail.Metadata.Name, "---")
	obspathArray := strings.Split(project.Manager.ObsBucketName, "/")
	obspath := obspathArray[1]
	obsUrl := obspathArray[2] + "/projects/" + strings.Join(nameList[:2], "/")
	var listObjectInputs obs.ListObjectsInput
	listObjectInputs.Prefix = obsUrl
	listObjectInputs.Bucket = obspath
	listObjectInputs.MaxKeys = 5000
	listObjectInputs.Marker = "marker"
	listObjectOutputs, err := project.obsClient.ListObjects(&listObjectInputs)
	defer project.obsClient.Close()
	if err != nil {
		return err
	}
	var deleteObjectinput obs.DeleteObjectInput
	for _, v := range listObjectOutputs.Contents {
		// 循环遍历删除
		if strings.Contains(v.Key, jobDetail.Metadata.Id) {
			deleteObjectinput.Key = strings.Replace(v.Key, obsUrl, "", 1)
			deleteObjectinput.Bucket = obspath
			_, err := project.obsClient.DeleteObject(&deleteObjectinput)
			if err != nil {
				return err
			}
		}
	}
	if listObjectOutputs.StatusCode != http.StatusOK {
		return fmt.Errorf("删除jobID:%s的日志的时候出现异常", jobDetail.Metadata.Id)
	}
	return nil
}

// 删除羲和某人某项目下的所有训练任务
// xiheUserName 羲和的用户名
// xiheProject 羲和的项目名
// 从offset一直删到limit
func (project *ModelArtsProject) DeleteAllTrainingJob(xiheUserName, xiheProject string, offset, limit int) error {
	allJobs, err := project.ListTrainingJobs(xiheUserName, xiheProject, offset, limit)
	if err != nil {
		return err
	}
	for _, job := range allJobs.Items {
		err = project.DeleteTrainingJob(job.Metadata.Id)
		if err != nil {
			return err
		}
	}
	return nil
}

// 按照羲和项目的前端格式要求输出
func (job *JobDetail) Format() (displayjob *DisplayJob) {
	displayjob = new(DisplayJob)
	displayjob.TrainID = job.Metadata.Id
	namesplit := strings.Split(job.Metadata.Name, "---")
	if len(namesplit) < 3 {
		log.Printf("编号为%s的job的名字[%s]格式不符合羲和规定", job.Metadata.Id, job.Metadata.Name)
		displayjob.InsanceName = job.Metadata.Name
	} else {
		displayjob.InsanceName = namesplit[2]
	}
	displayjob.CreateTime = convertTime(job.Metadata.CreateTime)
	displayjob.Status = easyStatus(job.Status.Phase)
	displayjob.Runtime = convertRuntime(job.Status.Duration)
	displayjob.EngineName = job.Algorithm.Engine.EngineId
	displayjob.NodeCount = strconv.Itoa(job.Spec.Resource.NodeCount)
	displayjob.Flavor = getTrainInstanceTypesByVersion(job.Algorithm.Engine.EngineVersion, job.Spec.Resource.FlavorId)
	displayjob.Description = job.Metadata.Description
	log_obs_url := strings.Split(job.Spec.LogExportPath.ObsUrl, "/")
	if len(log_obs_url) < 6 {
		// 正常的情况是不会小于6个的，如果实在小于6个，先空白输出
		displayjob.LogFile = ""
	} else {
		displayjob.LogFile = strings.Join(log_obs_url[6:], "/")
	}
	return
}
func (displayjob *DisplayJob) SimplePrint() {

	log.Printf("编号:%s,名称:%s,状态:%s,创建时间:%s,运行:%s,引擎:%s,描述:%s", displayjob.TrainID, displayjob.InsanceName, displayjob.Status, displayjob.CreateTime, displayjob.Runtime, displayjob.EngineName, displayjob.Description)
}

// 输出json 文本格式
func (displayjob *DisplayJob) ToJson() string {
	jsonBytes, err := json.Marshal(displayjob)
	if err != nil {
		return ""
	}
	return string(jsonBytes)
}

// 启动羲和项目下的评估Aim
func (project *ModelArtsProject) StartNewAim(xiheUserName, xiheProjectName, jobID string) (aim *Aim) {
	aim = NewAim(xiheUserName, xiheProjectName, jobID)
	aim.modelArtsProject = project
	return
}
